#include "PLSMetricLearner.h"
#include "MetricLearner.h"
#include "MLKRGeneralizer.h"
#include "MREAgent.h"
#include "Util.h"


PLSMetricLearner::PLSMetricLearner(MLKRGeneralizer* p)
: MultiVariateMetricLearner(p){


	//initialize w 
	w.resize(parent->action_number ); 
	for(int i=0; i< parent->action_number; i++)
		w[i] = new MatlabMatrix<double>(parent->obs_dim, parent->obs_dim); 

	threadW = new MatlabMatrix<double>(parent->obs_dim, parent->obs_dim); 
}

PLSMetricLearner::~PLSMetricLearner(void)
{

	for(int i=0; i< parent->action_number; i++)
		delete w[i]; 

	//w is actually a pointer to threadW, so we dont need to delete threadw separately
}

void PLSMetricLearner::submitParams(int action)
{
	if (w[action])
		delete w[action]; 
	w[action] = threadW; 
}

int PLSMetricLearner::learnTransformation(ANNpointArray& data, ANNpointArray& targets, int size, int action)
{
	int newdim = parent->obs_dim; 
#ifdef USE_MATLAB
		double** x = data;
		double** y = targets; 
		int obs_dim = parent->obs_dim; 

		//create variable x (note that since matlab stores column-wise and we have row-wise here, and it's more
		//efficient to transpose the matrix in matlab we send data as if it is obs_dim*dataSize while it's the
		//other way around. it is crucial that we transpose data in matlab before we do anything. 
		mxArray* xm = mxCreateDoubleMatrix(obs_dim, size , mxREAL); 
		double* tmpPtr = (double*) mxGetPr(xm); 
		memcpy(tmpPtr, x[0], obs_dim*size*sizeof(double)); 
		int res = engPutVariable(getEngine(), "x", xm); 

		//same for y
		mxArray* ym = mxCreateDoubleMatrix(obs_dim, size , mxREAL); 
		tmpPtr = (double*) mxGetPr(ym); 
		memcpy(tmpPtr, y[0], obs_dim*size*sizeof(double)); 
		res = engPutVariable(getEngine(), "y", ym); 

		//transpose x and y
		engEvalString(getEngine(),"x = x';y=y';");

		mxDestroyArray(xm); 
		mxDestroyArray(ym); 


		//run the dimension reduction and transpose again 
		char* tmpStr = new char[160]; 
		sprintf(tmpStr, "[xstmp, wtmp] = plsRegression(x,y,%lf); xstmp=xstmp'; xs{%d} = xstmp; w{%d} = wtmp;",0.9, action+1,action+1);  
//		sprintf(tmpStr,"[xl{%d},yl{%d},xs{%d},ys{%d},beta{%d},PCTVAR{%d}, mse{%d}, stats{%d}] = plsregress(x,y,%d);",i+1,i+1,i+1,i+1,i+1,i+1,i+1,i+1,parent->obs_dim ); 
		engEvalString(getEngine(), tmpStr);
		delete tmpStr; 

		//just for debugging: backup x and y in matlab
		tmpStr = new char[80]; 
		sprintf(tmpStr, "xbackup{%d} = x; ybackup{%d} = y; dim=size(xstmp,1);", action+1, action+1); 
		engEvalString(getEngine(), tmpStr); 
		delete tmpStr; 


		mxArray* dimm = engGetVariable(getEngine(), "dim"); 
		mxArray* xstmpm = engGetVariable(getEngine(), "xstmp"); 
		double dim = 0; 
		memcpy(&dim, mxGetPr(dimm), sizeof(double)); 

		newdim = dim; 

		//extract w into its c++ corresponding variable
		mxArray* wm = engGetVariable(getEngine(), "wtmp");

		//create a tmp w vector 
		threadW = new MatlabMatrix<double>(obs_dim, newdim); 
		double* wPtr = threadW->getStorage(); 
		memcpy(wPtr, mxGetPr(wm), obs_dim*newdim*sizeof(double)); 


		//reallocate data using new dimension 
		if(data)
			annDeallocPts(data); 
		data = annAllocPts(size, newdim); 

		//copy data
		memcpy(data[0], mxGetPr(xstmpm), size*newdim*sizeof(double)); 
#endif
	return newdim; 
}


// this function is similar to transform function but does the job in matlab. 
// another difference is that this one can take multiple observations at a time. 
void PLSMetricLearner::transformInMatlab(ANNpointArray& data, int size, Action a)
{
#ifdef USE_MATLAB  
	//this does exactly as the transform function except that it does it in matlab
	mxArray* xm = mxCreateDoubleMatrix(parent->obs_dim, size , mxREAL); 
	double* tmpPtr = (double*) mxGetPr(xm); 
	memcpy(tmpPtr, data[0] , parent->obs_dim*size*sizeof(double));

	int res = engPutVariable(getEngine(), "tmpObs", xm); 
	char* tmpStr = new char[60]; 
	sprintf(tmpStr, "tmpObs = tmpObs*w{%d};", a+1); 
	engEvalString(getEngine(), tmpStr);

	mxArray* xpm = engGetVariable(getEngine(), "tmpObs");

	//reallocate data using new dimension
	if (data)
		annDeallocPts(data); 
	data = annAllocPts(size, parent->reducedDimensions[a]); 

	memcpy(data[0], mxGetPr(xpm), size*parent->reducedDimensions[a]*sizeof(double)); 
	delete[] tmpStr; 
#endif 
}

// compute newst*w and put it back into newst. 
void PLSMetricLearner::transform(Observation newst, Action a)
{
	MatlabMatrix<double>* ww = w[a]; 
	int rdim = ww->nCols();				//new dimension  
	int obs_dim = ww->nRows();		//always obs_dim 

	Observation_type* tobs = new Observation_type[obs_dim]; //create a new place for newst
	memset(tobs, 0, obs_dim*sizeof(double)); 

	//compute newst*w
	for(int i=0; i< rdim; i++)
		for(int j=0; j< obs_dim; j++)
			tobs[i] += newst[j]*ww->operator ()(j,i); 

	memcpy(newst, tobs, obs_dim*sizeof(double)); 
	delete[] tobs; 
}
